#!/usr/bin/env python3
import argparse
import socket
import struct
import os
import sys
import pdb
import re
import ipaddress
import inspect
import binascii
from datetime import datetime
server_ip = "127.0.0.1"
server_port = 60000
######################################################################################################
def DLOG(message):
    frame = inspect.stack()[1]
    filename = frame.filename
    lineno = frame.lineno
    timestamp = datetime.now().strftime('%y%m%d %H:%M:%S')
    message = message.replace('\n', '')
    print(f"+|{timestamp}| {message}")
    #rint(f"+++: {timestamp} ({filename}:{lineno}) {message}")
def ipv4_to_int(ip_str):
    ip_obj = ipaddress.IPv4Address(ip_str)
    ip_int = int(ip_obj)
    return ip_int
def ipv6_to_int(ip_str):
    ip_obj = ipaddress.IPv6Address(ip_str)
    ip_int = int(ip_obj)
    return ip_int

def convert_to_decimal(input_str):
    if input_str.startswith("0x"):
        return int(input_str, 16)
    else:
        return int(input_str)

lines=0
bytes=0
def serialize_value(line_key, line_value, line_note):
    ret_fmt = None 
    ret_off = 18
    global lines,bytes
    if line_key == 'resv1':
        ret_fmt = 'B'
        length = 1
        ret_value = convert_to_decimal(line_value)
        ret_value = int(ret_value).to_bytes(1, byteorder='big', signed=False)
    elif line_key.startswith('fix') or line_key.startswith('str'):
        length=len(line_key)
        if length == 3:
            length=len(line_value)
            ret_fmt = f"{length}s"
            ret_value = line_value.encode('utf-8')
        else:
            length = int(line_key[3:])
            ret_fmt = f"{length}s"
            ret_value = line_value[:length]  # Truncate to specified length if necessary
            ret_value = ret_value.ljust(length, '\x00')  # Left-pad with spaces to fixed length
            ret_value = ret_value.encode('utf-8')
    elif line_key == 'int1' or line_key == 'type' or line_key == 'resv2':
        ret_fmt = 'B'
        length = 1
        ret_value = convert_to_decimal(line_value)
        ret_value = int(ret_value).to_bytes(1, byteorder='big', signed=False)
    elif line_key == 'int2':
        ret_fmt = 'H'
        length = 2
        ret_value = convert_to_decimal(line_value)
        ret_value = int(ret_value).to_bytes(2, byteorder='big', signed=False)
    elif line_key == 'int4' or line_key == 'no' or line_key == 'seq' or line_key == 'length':
        ret_fmt = 'I'
        length = 4
        ret_value = convert_to_decimal(line_value)
        ret_value = int(ret_value).to_bytes(4, byteorder='big', signed=False)
    elif line_key == 'signature':
        length=len(line_value)
        ret_fmt = f"{length}s"
        ret_value = line_value.encode('utf-8')
    elif line_key == 'ipv4':
        ret_fmt = 'I'
        length = 4
        ret_value = ipv4_to_int(line_value)
        ret_value = int(ret_value).to_bytes(4, byteorder='big', signed=False)
    elif line_key == 'ipv6':
        ret_fmt = 'QQ'
        length = 16
        ret_value = ipv6_to_int(line_value)
        ret_value = int(ret_value).to_bytes(16, byteorder='big', signed=False)
    else:
        raise ValueError(f"Unsupported format string: {line_key}")
    lines = lines + 1
    bytes = bytes + length 
    bodys = bytes - 18
    DLOG(f"{lines:<4}|{bytes:<4}|{bodys:<4}|{ret_fmt:<4}|{line_key:<10}|{line_value:<16}|{line_note}")
    return ret_fmt, ret_value

find_length_line = False
def create_uc_pkt_format_and_serialize_values(file_path):
    global find_length_line
    body_format = []
    body_data = []
    serialized_data = []

    # open uc txt file
    ver_resv = 0
    with open(file_path, 'r', encoding='utf-8-sig') as f:
        for line in f:
            # 过滤无效行
            line_strip = line.strip()
            if line_strip.startswith('#') or line_strip.isspace() or not line_strip.strip():
                continue
            if len(line_strip) < 2:
                continue
            # 获取行内容
            #DLOG(f"LINE: {line_strip}:{len(line_strip)}]") 
            line_array = line_strip.split('#')
            if len(line_array) < 2:
                line_note = ""
            else:
                line_note = line_array[1].strip("\n")
            line_kv = line_array[0]
            if find_length_line is False: 
                parts = re.split('[:#]', line_kv, flags=re.UNICODE)
            else:
                parts = re.split('[,#]', line_kv, flags=re.UNICODE)
            if line.startswith('length:'):
                find_length_line =  True
            if len(parts) < 2:
                raise ValueError(f"Invalid line format: {line_strip}")
            # 解析行内容, 构造format
            line_key, line_value = parts[:2]
            line_key = line_key.strip()
            line_key = line_key.replace('\ufeff', '').strip()
            line_value = line_value.strip()
            if len(line_value) < 1:
                continue
            if line_key == 'ver':
                ver_resv = convert_to_decimal(line_value)
                continue
            if line_key == 'resv1':
                resv = convert_to_decimal(line_value)
                ver_resv = (resv<< 4) | ver_resv
                line_value = str(ver_resv)

            #serialize line
            ret_fmt, ret_value = serialize_value(line_key, line_value, line_note)
            if ret_value is not None and ret_fmt is not None:
                body_format.append(ret_fmt)
                body_data.append(ret_value)
    # 计算total_length
    pkt_format = '>' + ''.join(body_format)
    total_length = struct.calcsize(''.join(pkt_format))
    new_length_bytes = total_length.to_bytes(4, byteorder='big')
    pkt_data = b''.join(body_data)
    pkt_data = pkt_data[:14] + new_length_bytes + pkt_data[18:]
    pkt_data = [pkt_data]
    return ''.join(pkt_format), pkt_data

def send_request_to_server(server_ip, server_port, uc_file, receive_timeout=1):
    # uc format
    uc_format, uc_data = create_uc_pkt_format_and_serialize_values(uc_file)
    DLOG(f"uc format: {uc_format}")
    # uc text 
    packed_info = b''.join(uc_data)
    unpack_info = struct.unpack(uc_format, packed_info)
    DLOG(f"uc text  : {unpack_info}")
    # uc stream
    data_slice = packed_info[4:5]
    data_type = struct.unpack("!B", data_slice)[0]
    DLOG(f"uc stream: {hex(data_type)}({len(packed_info)} bytes): {packed_info.hex()}")

    # save uc data to file
    ucfile_dir = 'uc_files'
    if not os.path.exists(ucfile_dir):
        os.mkdir(ucfile_dir)
    uc_msg = struct.Struct(uc_format).pack(*unpack_info)  # 打包报文
    uc_to_file = os.path.join(ucfile_dir, re.sub(r'.txt$', '.uc', os.path.basename(uc_file)))  # 获取uc文件路径
    DLOG(f"uc tofile: {uc_file} --> {uc_to_file}")
    with open(uc_to_file, 'wb') as uc_f:
        uc_f.write(uc_msg)  # 写uc文件

    # send uc data to socket
    DLOG(f"uc tosock: {uc_file} --> {server_ip}:{server_port}")
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        #ith socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
            # 打开连接
            s.connect((server_ip, server_port))
            client_ip, client_port = s.getsockname()[:2]
            server_ip, server_port = s.getpeername()[:2]
            # 发送报文
            s.sendall(packed_info)
            DLOG(f"uc tosock: send succ: {client_ip}:{client_port} --> {server_ip}:{server_port}\n")
            # 接收报文
            s.settimeout(receive_timeout)
            buffer_size = 4096
            received_data = bytearray()
            while False:
                try:
                    chunk = s.recv(buffer_size)
                except socket.timeout:
                    break;

                if not chunk:
                    break  # 数据接收完毕，服务器关闭连接或无更多数据
                received_data.extend(chunk)            
                data_slice = received_data[4:5]
                data_type = struct.unpack("!B", data_slice)[0]
                DLOG(f"uc stream recv: {hex(data_type)}({len(received_data)} bytes): {received_data.hex()}")
                DLOG(f"uc stream recv: succ")
            # 关闭连接
            s.close()
    except (socket.timeout, ConnectionRefusedError, socket.gaierror) as e:
        DLOG(f"Error connecting to the server: {e}")
        DLOG(f"uc stream send: fail")
        sys.exit(1)
    finally:
        s.close()

######################################################################################################
if __name__ == "__main__":
    if len(sys.argv) != 4:
        DLOG(f"Error: input param error")
        DLOG(f"usage: argv[0] <ip> <port> <uc_txt_file>")
        sys.exit(1)
    DLOG(f"args={sys.argv}")
    server_ip      = sys.argv[1]
    server_port    = int(sys.argv[2])
    uc_file        = sys.argv[3]
    send_request_to_server(server_ip, server_port, uc_file)
